﻿/* Copyright (C) Siemens AG, 2012. All Rights Reserved.
 * Author: Roland Eckl, Corporate Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;


namespace RVM.Infrastructure.Identification.Process
{
    /// <summary>
    /// IdentificationProvider based on Processes.
    /// There is only a single local exporter per process available.
    /// Note: Finding other processes uses process names.
    /// </summary>
    public class ProcessIdentificationProvider : IIdentificationProvider
    {
        #region Private Members

        /// <summary>
        /// EventWaitHandle which is set by exporting processes when starting exporting.
        /// Is used to raise the ExportingSource event.
        /// </summary>
        private static EventWaitHandle _listenerEventWaitHandle = null;

        /// <summary>
        /// EventWaitHandle which is used to signal identity changes to other processes.
        /// </summary>
        private static EventWaitHandle _exporterEventWaitHandle = null;

        /// <summary>
        /// Thread which waits for the wait handle to be set.
        /// </summary>
        private Thread _eventWaitHandleThread = null;

        /// <summary>
        /// Corresponding session object, which is shared among compatible importers and exporters.
        /// </summary>
        private Session _session = null;

        /// <summary>
        /// ProcessName of valid exporters (assuming all these processes have the same executable name)
        /// </summary>
        private string _processName = string.Empty;

        /// <summary>
        /// Event can be raised from the EventWaitHandle thread because it is also static.
        /// </summary>
        private static event ExportingSourceDelegate _exportSource;

        #endregion

        #region Public Members

        /// <summary>
        /// Public Accessor of the session's shared Id.
        /// </summary>
        public string SessionId
        {
            get
            {
                if (_session != null)
                    return _session.Id;
                return string.Empty;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor, takes the shared session Id for compatible importers and exporters.
        /// </summary>
        /// <param name="sharedSession"></param>
        public ProcessIdentificationProvider(Session sharedSession, string processName)
        {
            _session = sharedSession;
            _processName = processName;
            _exporterEventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, SessionId);
        }

        #endregion

        #region Public Methods and Events (IIdentificationProvider)

        /// <summary>
        /// ProcessIdentificationProvider offers only a single identity per process (process ID as identity)
        /// </summary>
        public IdentityScope Scope
        {
            get { return IdentityScope.OnePerProcess; }
        }

        /// <summary>
        /// The local ID is same as the process' ID.
        /// </summary>
        /// <returns>local process ID</returns>
        public string CreateNewIdentity()
        {
            return System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
        }

        /// <summary>
        /// Returns a list of all process IDs which seem to be possible exporters.
        /// Note: This list does NOT represent actually exporting processes, only candidates!
        /// </summary>
        /// <returns>List of process IDs</returns>
        public string[] GetExportingIdentities()
        {
            return GetExportingIdentities(_processName);
        }

        /// <summary>
        /// Returns a list of all process IDs which seem to be possible exporters.
        /// Note: This list does NOT represent actually exporting processes, only candidates!
        /// </summary>
        /// <param name="processName">Process name shared among exporters</param>
        /// <returns>List of process IDs</returns>
        public string[] GetExportingIdentities(string processName)
        {
            System.Diagnostics.Process[] p = GetCompatibleProcesses(processName);
            IEnumerable<string> result = from one in p select one.Id.ToString();

            return result.ToArray();
        }

        /// <summary>
        /// Checks whether an process with the given ID is a already available.
        /// </summary>
        /// <param name="identity">Id</param>
        /// <returns>TRUE, if a process with the given ID is running</returns>
        public bool IsExporting(string identity)
        {
            string[] valid = GetExportingIdentities();
            return valid.Any(x => x.Equals(identity));
        }

        /// <summary>
        /// Event is raised whenever an exporting processes sets the EventWaitHandle.
        /// Note: When accessing the UI, dispatch this call!
        /// </summary>
        public event ExportingSourceDelegate ExportingSource
        {
            add
            {
                if (!IsExportingSourceRegistered(value))
                {
                    _exportSource += value;
                    StartListening();
                }
            }
            remove
            {
                _exportSource -= value;
                if (!HasExportingSourceListeners())
                    StopListening();
            }
        }

        /// <summary>
        /// Signals an identity.
        /// Uses WaitEventHandle to signal some identity changed. No detailed information
        /// contained in signal.
        /// </summary>
        /// <param name="start">Identity was started (=TRUE) or stopped</param>
        public void Signal(bool start)
        {
            if (start)
                _exporterEventWaitHandle.Set();
            else
                _exporterEventWaitHandle.Reset();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Thread that blocks until the EventWaitHandle is set by an exporting process.
        /// Raises the registered event handlers.
        /// </summary>
        /// <param name="guid">GUID of the EventWaitHandle used accross importers and exporters</param>
        private static void WaitHandleThreadProc(object guid)
        {
            do
            {
                _listenerEventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, guid as string);
                _listenerEventWaitHandle.WaitOne();

                // Signal all event handlers!
                if (_exportSource != null)
                {
                    foreach (Delegate registered in _exportSource.GetInvocationList())
                    {
                        registered.DynamicInvoke();
                    }
                }

                // Keep the gate open at least one second.
                Thread.Sleep(new TimeSpan(0,0,1));
                _listenerEventWaitHandle.Reset();
            } while (true);
        }

        /// <summary>
        /// Starts listening on the EventWaitHandler. Starts therefore the corresponding thread.
        /// </summary>
        private void StartListening()
        {
            if (_eventWaitHandleThread == null)
            {
                _eventWaitHandleThread = new Thread(WaitHandleThreadProc);
                _eventWaitHandleThread.Name = "ProcessIdentificationProvider_eventWaitHandleThread";
                _eventWaitHandleThread.IsBackground = true;
                _eventWaitHandleThread.Start(SessionId);
            }
        }

        /// <summary>
        /// Stops listening on the EventWaitHandler. Stops therefore the corresponding thread.
        /// </summary>
        private void StopListening()
        {
            if(_eventWaitHandleThread != null)
            {
                _eventWaitHandleThread.Abort();
                _eventWaitHandleThread = null;
            }
        }

        #region Helpers

        /// <summary>
        /// Finds all processes with a given process name within the current user's Windows session.
        /// </summary>
        /// <param name="processName">Process name to find</param>
        /// <returns>Array of all processes with given name on local session</returns>
        private static System.Diagnostics.Process[] GetCompatibleProcesses(string processName)
        {
            System.Diagnostics.Process[] all = System.Diagnostics.Process.GetProcesses();
            System.Diagnostics.Process self = System.Diagnostics.Process.GetCurrentProcess();

            var res = from p in all where p.ProcessName.Equals(processName) && (p.SessionId == self.SessionId) && !p.Equals(self) select p;

            return res.ToArray();
        }
        /// <summary>
        /// Checks if someone has registered at the ExportingSource event.
        /// </summary>
        /// <returns>TRUE, if at least one handler got registered</returns>
        private static bool HasExportingSourceListeners()
        {
            if (_exportSource != null && _exportSource.GetInvocationList().Count() > 0)
                return true;
            return false;
        }

        /// <summary>
        /// Checks if some specific handler is already registered.
        /// This ensures that the same handler is not registered twice.
        /// </summary>
        /// <param name="handler">Delegate to check</param>
        /// <returns>TRUE, if handler is already registered</returns>
        private static bool IsExportingSourceRegistered(Delegate handler)
        {
            if (_exportSource != null)
            {
                foreach (Delegate existingHandler in _exportSource.GetInvocationList())
                {
                    if (existingHandler == handler)
                        return true;
                }
            }
            return false;
        }

        #endregion

        #endregion
    }
}
